////////////////////////////////////////////////////////////////////////////////
//
//  FLEX USER GROUP HOKURIKU
//  Copyright 2009 Flex User Group Hokuriku
//  All Rights Reserved.
// 
//  NOTICE: Flex User Group Hokuriku permits you to use, 
//  modify, and distribute this file 
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package net.fxug.hokuriku.chocolat.view
{
    import flash.desktop.NativeApplication;
    import flash.display.NativeWindow;
    import flash.display.Screen;
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.TimerEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.utils.Timer;

    /**
     * 通知ウィンドウの管理クラス
     *
     * @see http://www.adobe.com/devnet/air/flex/quickstart/creating_toast-style_windows.html
     * @author shoito
     */
    public class NotificationManager extends EventDispatcher
    {
        private static var instance:NotificationManager;

        /** 「へぇ」 ボタンによるリアクションを示す */
        public static const NOTIFY_GOOD:String = "good";

        /** 「ぶぅ」ボタンによるリアクションを示す */
        public static const NOTIFY_BAD:String = "bad";

        /** 通知ウィンドウの表示残時間カウントイベント */
        public static const LIFE_TICK_EVENT:String = "lifeTick";

        /** 通知ウィンドウの画面端からの距離 */
        private static const gutter:int = 10;

        /** 通知ウィンドウを左から表示 */
        private static const LEFT:String = "left";

        /** 通知ウィンドウを上から表示 */
        private static const TOP:String = "top";

        /** 通知ウィンドウを右から表示 */
        private static const RIGHT:String = "right";

        /** 通知ウィンドウの表示時間(second) */
        private const timeToLive:uint = 3;

        private var lifeTicks:uint = 0;

        private var lifeTimer:Timer = new Timer(1000);

        public function NotificationManager():void
        {
            if (instance != null)
            {
                throw new IllegalOperationError("Only one NotificationWindowManager instance should be instantiated");
            }

            lifeTimer.addEventListener(TimerEvent.TIMER, lifeTimer_timerHandler);
            lifeTimer.start();
        }

        public static function getInstance():NotificationManager
        {
            if (instance == null)
            {
                instance = new NotificationManager();
            }

            return instance;
        }

        /**
         * 通知ウィンドウを表示する。<br />
         * Goodの通知は画面左側、Badの通知は画面右側にウィンドウが表示される。
         *
         * @param notifyType 通知の種類
         * @param comment 表示するコメント
         * @param userName 表示するユーザー名
         * @param link 関連リンク情報
         */
        public function notify(notifyType:String = NOTIFY_GOOD, comment:String = "", userName:String = "", link:String = null):void
        {
            var window:NotificationWindow = new NotificationWindow(this, notifyType, comment, userName, link);
            var from:String = findStartDirection();
            var startPoint:Point = findStartPoint(window.bounds, from);
            var endPoint:Point = findEndPoint(window.bounds, from, startPoint);

            window.timeToLive = timeToLive;
            window.x = startPoint.x;
            window.y = startPoint.y;

            window.visible = true;
            animate(window, from, endPoint);
        }

        private function findStartDirection():String
        {
            var froms:Array = [LEFT, TOP, RIGHT];
            var index:int = Math.floor(Math.random() * froms.length);
            return froms[index];
        }

        private function findStartPoint(size:Rectangle, from:String):Point
        {
            var startX:Number;
            var startY:Number;

            switch (from)
            {
                case LEFT:
                    startX = -size.width;
                    startY = Math.random() * Screen.mainScreen.bounds.height - size.height;
                    break;
                case RIGHT:
                    startX = Screen.mainScreen.bounds.width + size.width;
                    startY = Math.random() * Screen.mainScreen.bounds.height - size.height;
                    break;
                default:
                    startX = Math.random() * Screen.mainScreen.bounds.width - size.width;
                    startY = -size.height;
                    break;
            }

            var startPoint:Point = new Point(startX, startY)
            return startPoint;
        }

        private function findEndPoint(size:Rectangle, from:String, start:Point = null):Point
        {
            var endX:Number;
            var endY:Number;

            switch (from)
            {
                case LEFT:
                    endX = gutter;
                    endY = (start) ? start.y : Math.random() * Screen.mainScreen.bounds.height - size.height;
                    break;
                case RIGHT:
                    endX = Screen.mainScreen.bounds.width - size.width - gutter;
                    endY = (start) ? start.y : Math.random() * Screen.mainScreen.bounds.height - size.height;
                    break;
                default:
                    endX = (start) ? start.x : Math.random() * Screen.mainScreen.bounds.width - size.width;
                    endY = gutter;
                    break;
            }

            var endPoint:Point = new Point(endX, endY)
            return endPoint;
        }

        private function animate(window:NativeWindow, from:String, animateTo:Point):void
        {
            switch (from)
            {
                case LEFT:
                    animateFromLeft(window, animateTo);
                    break;
                case RIGHT:
                    animateFromRight(window, animateTo);
                    break;
                default:
                    animateFromTop(window, animateTo);
                    break;
            }
        }

        /**
         * 画面左からの通知ウィンドウをアニメーション終了位置までアニメーションする
         *
         * @param window 通知ウィンドウ
         * @param animateTo アニメーション終了位置
         */
        private function animateFromLeft(window:NativeWindow, animateTo:Point):void
        {
            var dX:Number;
            var animate:Function = function(event:Event):void
                {
                    dX = (window.x - animateTo.x) / 4
                    window.x -= dX;

                    if (window.x >= animateTo.x)
                    {
                        window.x = animateTo.x;
                        window.stage.removeEventListener(Event.ENTER_FRAME, animate);
                    }
                };
            window.stage.addEventListener(Event.ENTER_FRAME, animate);
        }

        /**
         * 画面上からの通知ウィンドウをアニメーション終了位置までアニメーションする
         *
         * @param window 通知ウィンドウ
         * @param animateTo アニメーション終了位置
         */
        private function animateFromTop(window:NativeWindow, animateTo:Point):void
        {
            var dY:Number;
            var animate:Function = function(event:Event):void
                {
                    dY = (window.y - animateTo.y) / 4
                    window.y -= dY;

                    if (window.y >= animateTo.y)
                    {
                        window.y = animateTo.y;
                        window.stage.removeEventListener(Event.ENTER_FRAME, animate);
                    }
                };
            window.stage.addEventListener(Event.ENTER_FRAME, animate);
        }

        /**
         * 画面右からの通知ウィンドウをアニメーション終了位置までアニメーションする
         *
         * @param window 通知ウィンドウ
         * @param animateTo アニメーション終了位置
         */
        private function animateFromRight(window:NativeWindow, animateTo:Point):void
        {
            var dX:Number;
            var animate:Function = function(event:Event):void
                {
                    dX = (animateTo.x - window.x) / 4;
                    window.x += dX;

                    if (window.x <= animateTo.x)
                    {
                        window.x = animateTo.x;
                        window.stage.removeEventListener(Event.ENTER_FRAME, animate);
                    }
                };
            window.stage.addEventListener(Event.ENTER_FRAME, animate);
        }

        /**
         * 通知が消えないようにタイマーを一時停止する
         */
        public function pauseExpiration():void
        {
            lifeTimer.stop();
        }

        /**
         * 通知が消えないように一時停止しているタイマーを再開する
         */
        public function resumeExpiration():void
        {
            lifeTimer.start();
        }

        private function lifeTimer_timerHandler(event:Event):void
        {
            lifeTicks++;
            var tickEvent:Event = new Event(LIFE_TICK_EVENT, false, false);
            dispatchEvent(tickEvent);
        }

        /**
         * 描画領域が他の通知と被っているかどうか調べる
         *
         * @param testRect 通知描画予定領域
         * @return 他の通知と被ってる場合、trueを返す
         */
        private function isOccupied(testRect:Rectangle):Boolean
        {
            var occupied:Boolean = false;
            for each (var window:NativeWindow in NativeApplication.nativeApplication.openedWindows)
            {
                occupied = occupied || window.bounds.intersects(testRect);
            }
            return occupied;
        }
    }
}